Skip to content

feat(interest-agent): conversational onboarding UI - #6549

Open
capJavert wants to merge 1 commit into
mainfrom
feat/interest-agent-onboarding
Open

feat(interest-agent): conversational onboarding UI#6549
capJavert wants to merge 1 commit into
mainfrom
feat/interest-agent-onboarding

Conversation

@capJavert

@capJavert capJavert commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Client half of conversational agent onboarding. Pairs with dailydotdev/daily-api#4169 — merge that first.

Setting an agent up now happens in the same transcript you talk to it in afterwards, rather than a separate form.

What's here

Three block renderers. question (chips or free text, live only at the transcript tail — earlier ones dim to a ✓), brief (rewritable inline before it's accepted), and review (edits settings in place, then starts the first hunt).

No settings wizard. Settings questions arrive as the same question blocks the agent authors, so the client owns neither the list, the order, nor the "load my recent settings" logic — the API does. One renderer covers the whole flow and a reload resumes from the transcript.

Composer stays mounted but disabled unless an open question actually wants prose. Everything else is driven by its own controls, so the UI can't produce feedback no question is waiting on.

Enter advances whichever step is open, through one shared advanceOnboarding in the context. It's handled at the workspace level rather than on the composer, because a disabled field fires no keydown and clicking a chip moves focus off the composer — the reason the earlier version of this didn't work.

Status. onboarding reads as "Setting up" in the intro and the agent list instead of falling through to "Paused".

Also in here

  • Delivery / output-mode controls removed from onboarding and settings; notifications stays, as its own onboarding step and its own setting. OutputModesSection had no callers left and is deleted.
  • FOMO presets are four chips (0.2 / 0.5 / 0.8 / 0.95) and the settings slider's copy now bands to match, so it never describes a value differently from the chip that set it.
  • Bug fix: attachments.ts and replyText.ts collected posts from blocks by assuming anything that isn't text carries posts. The new block types made that a runtime crash on the composer's mention candidates. Replaced with an isPostsBlock guard, so a future block type is excluded by default instead of breaking whatever reads .posts.

Testing

267 interest specs pass; strict typecheck across the whole shared package (not just changed files — that's what let the .posts crash through) and lint are clean. Walked the flow locally against the API branch. Two AgentHomeScreen specs updated for the onboarding: true payload and the removed digest switch.

🤖 Generated with Claude Code

Preview domain

https://feat-interest-agent-onboarding.preview.app.daily.dev

Renders the new onboarding turns inside the existing transcript, so setting an
agent up is the same surface as talking to it afterwards. Pairs with
daily-api #interest-agent-onboarding.

- question, brief and review block renderers. Questions are chips or free
  text, live only at the tail; the brief can be rewritten inline before it is
  accepted; the review card edits settings in place and starts the first hunt.
- The composer stays mounted but is disabled unless an open question actually
  wants prose, so nothing can produce feedback no question is waiting on.
- Enter advances whichever step is open, via one shared advanceOnboarding in
  the context. It is handled at the workspace level because a disabled field
  fires no keydown and clicking a chip moves focus off the composer.
- Onboarding reads as "Setting up" in the intro and the agent list rather than
  falling through to "Paused".
- Delivery/output-mode controls are gone from onboarding and settings;
  notifications stays as its own step and its own setting.
- isPostsBlock guard: collecting posts from blocks assumed anything not text
  carries posts, which the new block types broke at runtime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview Aug 30, 2026 9:51pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
storybook Ignored Ignored Aug 30, 2026 9:51pm

Request Review

@capJavert capJavert left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Solid shape overall: one renderer per block type, the API owning the question list, and the isPostsBlock guard is the right fix for the .posts crash. The risk in this PR is concentrated in the workspace-wide Enter handler and in the new onboarding status leaking into surfaces outside the diff. Inline comments cover the per-line items; these are the cross-cutting ones.

Blocking

Deploy order, not just merge order. USER_INTEREST_FRAGMENT now requests brief and onboardingStep, and that fragment backs every interest read (list, single, create, update). If this ships before dailydotdev/daily-api#4169 is deployed, GraphQL validation fails on the fragment itself and the whole agent surface breaks, not just onboarding — same for $onboarding on createInterest and $questionId on sendInterestCommand. The description says merge the API first; worth confirming it is live in production before this merges, since a merge-order-only guarantee does not cover the gap.

Blast radius of the new onboarding status. Two places outside the diff still read it as "not active":

  • components/AgentSettingsMenu.tsx:51isRunning = status === UserInterestStatus.Active, so during setup the header menu shows "Resume the agent" with the switch off, and toggling it writes status: active. That drops the user out of onboarding mid-transcript while onboardingStep still points at an open step; afterwards activeQuestion/isReviewOpen are both false, the chips are dead, and there is no route back into setup. The switch should be hidden or disabled while onboarding.
  • components/AgentMark.tsx:19 (agentStateLabel, via AgentStatusTile in the workspace header) — reads "Paused" during onboarding, and the dot goes grey. That is the same fall-through the PR removes in AgentIntro and monitorItems, on the header the user is looking at during setup.

Non-blocking

  • Output modes removed with no path for existing agents. With OutputModesSection deleted, feed, post and digest are no longer editable anywhere. An agent created earlier with the digest email on keeps sending it and the user has no UI to turn it off. Is the API migrating those, or should the notifications section still expose the digest toggle?
  • Tests. The new routing (advanceOnboarding across question/brief/review, pendingAnswer seeding and reset, chips vs typed answers) is the risky surface here and has no spec; the two updated specs only assert the create payload. AgentContext.spec.tsx looks like the natural home for a couple of cases.

Verified

CI green on all checks; no merge conflicts; scope is coherent; shared-package consumers of the changed components enumerated; isPostsBlock covers every remaining .posts reader (attachments.ts, replyText.ts). Not verified: behaviour against the API branch, and the flow on a touch device.

Reviewed by AI.

}
};

globalThis.addEventListener('keydown', onKeyDown);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this handler swallows Enter on every focused button in the workspace.

For a focused <button>, keydown fires first and the click is its default action, so event.preventDefault() here cancels the button's own activation. The guard only exempts INPUT/TEXTAREA/contenteditable/dialog, so a keyboard user during onboarding gets:

  • Tab to Edit it → Enter runs confirmBrief() and accepts the brief instead of opening the editor.
  • Tab to Save and continue after a rewrite → advanceOnboarding() calls confirmBrief() with no argument, so the original brief is saved and the rewrite is dropped; the button's onClick never runs.
  • Tab to a Change row in the review card → Enter completes onboarding and starts the first hunt instead of expanding the row.
  • Tab to a single-select chip → the seeded pendingAnswer is submitted, not the focused chip.

It also captures the header buttons (Agent settings, close, tabs) for as long as an onboarding step is open.

Suggested direction: bail when the target is interactive, e.g. if (target?.closest('button, a, select, [role="button"], [contenteditable]')) { return; }. Those controls already invoke the same actions on activation, so nothing is lost and the "press Enter from anywhere" behaviour still holds for the empty-focus case.

Reviewed by AI.

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTailPending]);

// "Press Enter" has to hold anywhere on the screen: clicking a chip moves

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: useKeyboardNavigation(globalThis?.window, [['Enter', handler]], { disableOnTags: ['input', 'textarea'] }) already exists in src/hooks/useKeyboardNavigation.ts and is used two files over in AgentContentPane.tsx:264. It does the tag guard, the contenteditable check and the special-key/modifier filter for you. Any reason not to reuse it here rather than hand-rolling the listener?

Reviewed by AI.

size={ButtonSize.Small}
variant={ButtonVariant.Primary}
loading={isConfirmingBrief}
onClick={() => confirmBrief()}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: an open editor is invisible to advanceOnboarding, so a rewrite can be silently discarded.

draft is local to this component. While the textarea is open, any Enter with focus outside it — the user clicked the card background, or tabbed to a button — reaches the workspace handler, hits the isBriefOpen branch and calls confirmBrief() with no argument. The original brief is accepted, the draft disappears with no toast and no confirmation, and the flow moves to the review step.

Either lift the editing state into the context so advanceOnboarding can skip (or submit) it, or let the block register its own submit while editing.

Reviewed by AI.

!lastMessage.isPending &&
!!lastMessage.blocks?.some((block) => block.type === 'review');

const isBriefOpen =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: activeQuestion and isReviewOpen are both gated on isOnboarding, isBriefOpen is not. If the API leaves onboardingStep at brief after completeInterestOnboarding — or on a paused/stopped agent — the global Enter keeps confirming the brief and the brief card keeps its action buttons on a live agent. Making it isOnboarding && interest?.onboardingStep === UserInterestOnboardingStep.Brief costs nothing and stops the client depending on the API nulling that column.

Reviewed by AI.

className="shrink-0"
disabled={!feedback.trim() && !command}
disabled={
isComposerOff || (!feedback.trim() && !command && !canAdvance)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: on a free-text question canAdvance is true and isComposerOff is false, so the send button is enabled with an empty field — but onSubmit takes the !trimmed branch, and advanceOnboarding() returns false for a text question (choices is undefined, so picked is empty). The button is live and does nothing, with no feedback.

Related, for the same surface on touch: there is no Enter key there, so Press Enter to start hunting… and the or press Enter hints are dead copy on a phone. The chips, Looks right and Start hunting cover the flow, so it is only the copy and this one enabled-but-inert button that mislead. Worth either disabling send when a text question has no input, or routing send through the same path Enter takes.

Reviewed by AI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant